#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <unistd.h>

#include "common/surface_utils.h"
#include "common/b_spline3.h"

struct TreeInfo{
	std::vector<Point> topCurvePoints;
	std::vector<Point> baseCurvePoints;
	float trunkHeight;
	float trunkWidth;
	unsigned int trunkSegs;
	unsigned int revolutionStep;

	TreeInfo(){
		trunkHeight = 1.0f;
		trunkWidth = 0.1f;
		trunkSegs=5;
		revolutionStep=16;
	}
};

void printHelp();
void writeSurface(const Surface& surface,std::ostream& out,const std::string& mtlName);
void createTree(Surface& tree,const TreeInfo& info);

bool loadCurvePoints(std::vector<Point>& points, const std::string& path);
void loadDefaultTopCurve(TreeInfo& info);
void loadDefaultBaseCurve(TreeInfo& info);

int main(int argc,char** argv){
	std::string path;
	std::string bs_topPath;
	std::string bs_basePath;
	int top_flag = 0;
	int base_flag = 0;
	TreeInfo info;

	int c;
	opterr = 0;
	while ((c = getopt (argc, argv, "a:r:n:k:ho:b:t:")) != -1){
		switch (c){
			case 'a':
				info.trunkHeight = atof(optarg);
				break;
			case 'r':
				info.trunkWidth = atof(optarg);
				break;
			case 'n':
				info.trunkSegs = atoi(optarg);
				break;
			case 'k':
				info.revolutionStep = atoi(optarg);
				break;
			case 'o':
				path.append(optarg);
				break;
			case 'b':
				bs_basePath.append(optarg);
				base_flag = 1;
				break;
			case 't':
				bs_topPath.append(optarg);
				top_flag=1;
				break;

			default:
				printHelp();
				return 0;
		}
	}

	if(path==""){
		printHelp();
		return 0;
	}

	// La copa no utliza la curva por defecto
	if(top_flag){
		if(!loadCurvePoints(info.topCurvePoints,bs_topPath)){
			std::cerr << "No se pudo utilizar un archivo de datos" << std::endl;
			return -1;
		}
	}else{
		loadDefaultTopCurve(info);
	}

	// La copa no utliza la curva por defecto,
	if(base_flag){
		if(!loadCurvePoints(info.baseCurvePoints,bs_topPath)){
			std::cerr << "No se pudo utilizar un archivo de datos" << std::endl;
			return -1;
		}
	}else{
		loadDefaultBaseCurve(info);
	}

	Surface tree;
	std::ofstream out((path+".obj").c_str());
	createTree(tree,info);
	SurfaceUtils::instance().surfaceToObj(tree,out,path+".mtl");

	return 0;
}

void printHelp(){
	std::cout << "Modo de uso: " << std::endl;
	std::cout << "./treeGenerator [-artbh] -o output_file" << std::endl;
	std::cout << "Opciones:" << std::endl;
	std::cout << "\t -o : Obj de salida" << std::endl;
	std::cout << "\t -a : Alto del tronco del arbol" << std::endl;
	std::cout << "\t -r : Ancho del tronco del arbol" << std::endl;
	std::cout << "\t -n : Cantidad de segmentos del tronco del arbol" << std::endl;
	std::cout << "\t -k : Cantidad de angulos de revolucion para la copa" << std::endl;
	std::cout << "\t -t : Archivo con los datos para la copa del arbol" << std::endl;
	std::cout << "\t -b : Archivo con los datos para la base del arbol" << std::endl;
	std::cout << "\t -h : Muestra esta ayuda " << std::endl;
}

void createTree(Surface& tree,const TreeInfo& info){
	// Calculo el tronco.
	Surface baseSurface;

	SurfaceUtils::setRevolutionStep(info.revolutionStep);
	SurfaceUtils::instance().calculateRevolutionSurface(Point(0,0,0),Point(0,0,1),info.baseCurvePoints,baseSurface);
	SurfaceUtils::instance().calculateRevolutionSurfaceIndices(info.baseCurvePoints,baseSurface);

	// Calculo la copa
	Surface topSurface;
	SurfaceUtils::instance().calculateRevolutionSurface(Point(0,0,info.trunkHeight),Point(0,0,1),info.topCurvePoints,topSurface);
	SurfaceUtils::instance().calculateRevolutionSurfaceIndices(info.topCurvePoints,topSurface);

	// Junto amabas superficies en una sola
	// base
	tree.points = baseSurface.points;
	tree.texture = baseSurface.texture;
	tree.normals = baseSurface.normals;
	tree.indices = baseSurface.indices;
	tree.groupsSizes = baseSurface.groupsSizes;

	// copa( corrimiento de indices )
	for(unsigned int c=0;c<topSurface.indices.size();c+=3){
		tree.indices.push_back(topSurface.indices[c]+baseSurface.points.size()/3);
		tree.indices.push_back(topSurface.indices[c+1]);
		tree.indices.push_back(topSurface.indices[c+2]+baseSurface.normals.size()/3);
	}
	tree.groupsSizes.push_back(topSurface.groupsSizes.front());

	for(unsigned int c=0;c<topSurface.points.size();c++)
		tree.points.push_back(topSurface.points[c]);

	for(unsigned int c=0;c<topSurface.normals.size();c++)
		tree.normals.push_back(topSurface.normals[c]);

	for(unsigned int c=0;c<topSurface.texture.size();c++)
		tree.texture.push_back(topSurface.texture[c]);
}

bool loadCurvePoints(std::vector<Point>& points, const std::string& path){
	// Lee un archivo de texto con Points y arma la BSpline
	// o los deja como estan.
	// NO SE VERIFICAN INCONSISTENCIAS EN EL ARCHIVO
	std::ifstream file(path.c_str());
	if(!file)
		return false;

	std::string line;
	std::string type;
	file >> type;

	std::vector<Point> buffer;
	while(!file.eof()){
		file >> line;

		std::size_t posX = line.find_first_of(",");
		std::string numberX = line.substr(0,posX);
		std::size_t posY = line.find_first_of(",",posX+1);
		std::string numberY = line.substr(posX+1,posY-posX-1);
		std::string numberZ = line.substr(posY+1);

		Point p ( atof(numberX.c_str()), atof(numberY.c_str()) , atof(numberZ.c_str()) );
		buffer.push_back(p);
	}

	if(type=="RAW"){
		points=buffer;
	}

	else if (type=="BSPLINE"){
		BSpline3 curve;
		for(unsigned int w=0;w<buffer.size();w++)
			curve.addPoint(buffer[w]);
		for(CurveModel::Points::const_iterator it=curve.pointsBegin();it!=points.end();++it)
			points.push_back(*it);
	}

	else{
		return false;
	}


	return true;
}

void loadDefaultTopCurve(TreeInfo& info){
	info.topCurvePoints.push_back(Point(0.0f,0.0f,1.0f+info.trunkHeight));
	info.topCurvePoints.push_back(Point(0.0f,0.5f,0.5f+info.trunkHeight));
	info.topCurvePoints.push_back(Point(0.0f,1.0f,0.0f+info.trunkHeight));
}

void loadDefaultBaseCurve(TreeInfo& info){
	unsigned int num=info.trunkSegs;
	for(int c=num-1;c>=0;c--){
		info.baseCurvePoints.push_back(Point(0.0f,info.trunkWidth,info.trunkHeight/(num-c)));
	}
	info.baseCurvePoints.push_back(Point(0.0f,info.trunkWidth,0.0f));

}
